from abc import ABCMeta, abstractmethod
import zipfile
import plistlib
import os
import sys
import paramiko
import threading
from pathlib import Path


class AppAnalysis(metaclass=ABCMeta):
    file = None
    root = None
    names = None
    executable = None

    def __init__(self):
        pass

    def __init__(self, file_path):
        self.load_file(file_path)

    def load_file(self, file_path):
        try:
            self.file = zipfile.ZipFile(file_path)
            self.names = self.file.namelist()
        except Exception as e:
            print(e)

    def search_file(self, name):
        if self.file:
            pass
        else:
            return

    @abstractmethod
    def analysis(self):
        raise NotImplementedError


class ApkAppAnalysis(AppAnalysis):

    def __init__(self):
        super(ApkAppAnalysis, self).__init__()
        pass

    def __init__(self, file_path):
        super(ApkAppAnalysis, self).__init__(file_path)

    def analysis(self):
        pass


class IpaAppAnalysis(AppAnalysis):
    bundle_url_name = None
    devices_password = None

    def __init__(self):
        super(IpaAppAnalysis, self).__init__()

    def __init__(self, file_path):
        super(IpaAppAnalysis, self).__init__(file_path)

    def get_file(self, name):
        if self.file:
            return self.file.open(name)
        else:
            return

    def get_root_dir(self):
        if self.file:
            index = self.names.index('Payload/')
            directory = self.names[index + 1]
            return directory
        else:
            return ''

    def set_device_password(self, password):
        self.devices_password = password

    def analysis(self):
        directory = self.get_root_dir()
        info = self.get_file(directory + 'Info.plist')
        plist = plistlib.loads(info.read())

        result = dict()
        app_dir = os.getcwd() + '/apps/' + plist['CFBundleDisplayName']
        result.update({'应用名': plist['CFBundleDisplayName']})
        result.update({'版本号': plist['CFBundleShortVersionString']})
        result.update({'传输安全': str(plist['NSAppTransportSecurity']['NSAllowsArbitraryLoads'])})
        result.update({'可执行程序名称': plist['CFBundleExecutable']})

        self.bundle_url_name = plist['CFBundleIdentifier']
        executable = directory + plist['CFBundleExecutable']

        self.file.extract(executable, app_dir)
        self.permission(result, plist)
        self.symbol(result, app_dir + '/'+executable)
        self.compile(result, app_dir + '/'+executable)

        # 端口映射 iproxy 2222 22
        # self.proxy_forward()

        # 上传shell脚本
        self.exc_cmd('./script.sh copy_to_ios  '+self.devices_password+' script.sh')

        # 连接ios设备
        ios = IosDevicesManager(self.devices_password)
        ios.connect()
        ios.exec_cmd('chmod 777 script.sh ')

        app_data_path = ios.exec_cmd('./script.sh find_run_data ' + self.bundle_url_name)
        file_list = ios.exec_cmd('./script.sh get_file ' + app_data_path[0])

        download_file = []
        for item in file_list:
            if '.plist' in item or '.db' in item or '.sqlite' in item:
                download_file.append(item)

        for item in download_file:
            self.exc_cmd('./script.sh copy_to_pc '+self.devices_password+' ' + item + ' ' + app_dir + '/')
            file_name = item[item.rfind('/'):]
            file = Path(app_dir + file_name)
            if file.exists():
                result.update({item: '下载成功'})
            else:
                result.update({item: '下载失败'})

        return result

    @staticmethod
    def exc_cmd(cmd):
        result = os.popen(cmd)
        res = result.read()
        result = []
        for line in res.splitlines():
            result.append(line)
        return result

    @staticmethod
    def permission(result, plist):

        if 'NSCameraUsageDescription' in plist:
            result.update({'访问摄像头权限': plist['NSCameraUsageDescription']})

        if 'NSMicrophoneUsageDescription' in plist:
            result.update({'访问摄像头权限': plist['NSMicrophoneUsageDescription']})

        if 'NSLocationAlwaysAndWhenInUseUsageDescription' in plist:
            result.update({'访问定位权限': plist['NSLocationAlwaysAndWhenInUseUsageDescription']})

        if 'NSLocationWhenInUseUsageDescription' in plist:
            result.update({'访问定位权限': plist['NSLocationWhenInUseUsageDescription']})

        if 'NSContactsUsageDescription' in plist:
            result.update({'访问通讯录权限': plist['NSContactsUsageDescription']})

        if 'NSPhotoLibraryUsageDescription' in plist:
            result.update({'访问相册权限': plist['NSPhotoLibraryUsageDescription']})

        if 'NSAppleMusicUsageDescription' in plist:
            result.update({'访问媒体资料库权限': plist['NSAppleMusicUsageDescription']})

        if 'NSBluetoothPeripheralUsageDescription' in plist:
            result.update({'访问蓝牙权限': plist['NSBluetoothPeripheralUsageDescription']})

        if 'NSCalendarsUsageDescription' in plist:
            result.update({'访问日历权限': plist['NSCalendarsUsageDescription']})

        if 'NSHealthShareUsageDescription' in plist:
            result.update({'访问健康分享权限': plist['NSHealthShareUsageDescription']})

        if 'NSHealthUpdateUsageDescription' in plist:
            result.update({'访问健康更新权限': plist['NSHealthUpdateUsageDescription']})

        if 'NSLocationAlwaysUsageDescription' in plist:
            result.update({'始终访问位置权限': plist['NSLocationAlwaysUsageDescription']})

        if 'NSLocationUsageDescription' in plist:
            result.update({'使用位置权限': plist['NSLocationUsageDescription']})

        if 'NSLocationWhenInUseUsageDescription' in plist:
            result.update({'使用期间访问位置权限': plist['NSLocationWhenInUseUsageDescription']})

        if 'NSMicrophoneUsageDescription' in plist:
            result.update({'访问麦克风权限': plist['NSMicrophoneUsageDescription']})

        if 'NSMotionUsageDescription' in plist:
            result.update({'访问运动与健身': plist['NSMotionUsageDescription']})

        if 'NSMicrophoneUsageDescription' in plist:
            result.update({'访问麦克风权限': plist['NSMicrophoneUsageDescription']})

        if 'NSRemindersUsageDescription' in plist:
            result.update({'访问提醒事项权限': plist['NSRemindersUsageDescription']})

    def log(self):
        pass

    @staticmethod
    def keyboard(result, plist):
        pass

    def symbol(self, result, executable):
        ret = self.exc_cmd('nm ' + executable + ' | grep symbols')
        for item in ret:
            if item.find('symbols') != -1:
                result.update({'符号表信息泄漏': '存在符号表泄露'})
                break
            else:
                result.update({'符号表信息泄漏': '不存在符号表泄露'})

    def compile(self, result, executable):
        ret = self.exc_cmd('otool -hv ' + executable)
        has_pie = False
        for item in ret:
            if item.find('PIE') != -1:
                has_pie = True

        ret = self.exc_cmd('otool -I -V ' + executable + ' | grep stack')
        has_stack = False
        for item in ret:
            if item.find('stack') != -1:
                has_pie = True

        ret = self.exc_cmd('otool -I -V ' + executable + ' | grep release')
        has_release = False
        for item in ret:
            if item.find('release') != -1:
                has_pie = True

        if has_pie and has_stack and has_release:
            result.update({'编译选项检测': '安全'})
        elif not has_pie and has_stack and has_release:
            result.update({'编译选项检测': '中危'})
        elif not has_pie and not has_stack and has_release:
            result.update({'编译选项检测': '中危'})
        elif not has_pie and not has_stack and not has_release:
            result.update({'编译选项检测': '中危'})
        elif has_pie and not has_stack and has_release:
            result.update({'编译选项检测': '低危'})
        elif has_pie and not has_stack and not has_release:
            result.update({'编译选项检测': '中危'})
        elif has_pie and has_stack and not has_release:
            result.update({'编译选项检测': '低危'})

    @staticmethod
    def proxy_forward_thread():
        result = IpaAppAnalysis.exc_cmd('ps -A | grep iproxy')
        pid = 0
        for item in result:
            if -1 != item.find('iproxy 2222 22'):
                pid = item.split(' ')[0]
        if pid != 0:
            IpaAppAnalysis.exc_cmd('kill ' + pid)

        IpaAppAnalysis.exc_cmd('iproxy 2222 22')

    def proxy_forward(self):
        thread = threading.Thread(target=self.proxy_forward_thread)
        thread.start()


class IosDevicesManager:
    def __init__(self, password):
        self.hostname = '127.0.0.1'
        self.username = 'root'
        self.password = password
        self.ssh = paramiko.SSHClient()

    def __del__(self):
        self.ssh.close()

    def connect(self):
        self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        try:
            self.ssh.connect(hostname=self.hostname, username=self.username, password=self.password, allow_agent=True,
                             timeout=5, port=2222)
        except Exception as e:
            print(e)

    def exec_cmd(self, cmd):
        try:
            stdin, stdout, stderr = self.ssh.exec_command(cmd)
            exec_result = stdout.readlines()
            ret = []
            for item in exec_result:
                ret.append(item.replace('\n', ''))
            return ret
        except Exception as e:
            print(e)

# /private/var/mobile/Containers/Data/Application
# /var/containers/Bundle/Application/
# /private/var/mobile/Library/Keyboard/en-dynamic.lm/

if __name__ == '__main__':
    device = IosDevicesManager()
    device.connect()
    # while True:
    #     cmd = input()
    #     device.exec_cmd(cmd)